#include "../include/segment.h"

/*
pthread_mutex_t imutex = PTHREAD_MUTEX_INITIALIZER;

Segment* Segment::instance_ = NULL;

Segment& Segment::get_instance() {
  if (!instance_) {
    pthread_mutex_lock(&imutex);
    if (!instance_)
      instance_ = new Segment();
    pthread_mutex_unlock(&imutex);
  }
  return *instance_;
}
*/

Segment::Segment() :  
  second_gap_(192), 
  second_min_(64),
  first_max_(255),
  first_min_(128),
  all_words_sum_(0) {


  buffer_ = new char[MAX_BUFFER_SIZE];
  temp_ch_ = new char[MAX_BUFFER_SIZE];
  text_ = new char[MAX_BUFFER_SIZE];

  stop_word_file_ = Config::get_instance().get("stop_words");
  t2s_file_ = Config::get_instance().get("t2s_file");
  dict_file_ = Config::get_instance().get("dict_file");


  create_dict();


  if (init_stop_words() == -1)
    std::cerr << "init_stop_words failed!" << std::endl;

  if (init_t2s() == -1)
    std::cerr << "init_t2s failed!" << std::endl;


}

Segment::~Segment() {
  
  word* t_word = NULL;
  for (int i = 0; i < single_word_num_; ++i) {
    if (baseaddr_[i].word_address) {
      for (int j = 0; j < baseaddr_[i].hash_size; ++j) {
        if ((baseaddr_[i].word_address)[j].value != "") {
          t_word = (baseaddr_[i].word_address)[j].next;
          while (t_word) {
            word* t_t_word = t_word;
            t_word = t_word->next;
            delete t_t_word;
          }
        }
      }

      delete []baseaddr_[i].word_address;
    }
  }

  delete []baseaddr_;
  delete []buffer_;
  delete []temp_ch_;
  delete []text_;
}

int Segment::init_stop_words() {
  std::ifstream input(stop_word_file_.c_str(), std::ifstream::in);
  if (!input)
    return -1;

  stop_words_set_.clear();
  std::string line;
  while (getline(input, line)) {
    if (line.length() == 0)
      continue;
    stop_words_set_.insert(line);
  }

  input.close();
  return 0;
}

int Segment::init_t2s() {
  std::ifstream input(t2s_file_.c_str(), std::ifstream::in);
  if (!input)
    return -1;

  std::string line;
  char first[3] = { '\0' };
  char second[3] = { '\0' };
  while (getline(input,line)) {
    if(line.size() < 5)
      continue;
    first[0] = line[0];
    first[1] = line[1];
    second[0] = line[3];
    second[1] = line[4];
    map_t2s_.insert(make_pair(std::string(second), std::string(first)));

  }
  
  input.close();
  return 0;
}

bool Segment::is_stop_word(const std::string& word) {
  return (stop_words_set_.find(word) != stop_words_set_.end());
}

bool Segment::convert_word(const char* oword, char* newword) {
  std::map<std::string, std::string>::iterator it = map_t2s_.find(
      std::string(oword));
  if (it != map_t2s_.end()) {
    strcpy(newword, (it->second).c_str());
    return 0;
  } else
    return -1;
}

int Segment::convert_sentence(char* sentence) {
  int count = 0;
  if (sentence == NULL)
    return 0;

  char t[3] = { '\0' };
  char s[3] = { '\0' };
  for (int i = 0; sentence[i] != 0; ++i) {
    if (sentence[i] > 0)
      continue;
    if (sentence[i + 1] == '\0')
      return count;

    t[0] = sentence[i];
    t[1] = sentence[i + 1];

    if (convert_word(t, s) != 0) {
      ++i;
      continue;
    }

    count++;
    sentence[i] = s[0];
    sentence[i + 1] = s[1];
    ++i;
  }
  return count;
}

void Segment::create_dict() {

  std::ifstream infile(dict_file_.c_str(), std::ifstream::in);
  if (!infile) {
    std::cerr << "can not find dict!" << std::endl;
    exit(-1);
  }

  std::string s[MAX_WORDS_DIC_LINE], line;

  while (getline(infile, line)) {
    if (line.length() == 0) //or size
      continue;
    s[all_words_sum_] = line;
    ++all_words_sum_;
  }
  infile.close();

  QuickSort(s, 0, all_words_sum_ - 1); //sort all words

  single_word_num_ = second_gap_ * (first_max_ - first_min_ + 1); 

  try {
    baseaddr_ = new character[single_word_num_];
  } catch (const std::bad_alloc& ex) {
    std::cout << "request single word memory error!" <<ex.what()<< std::endl;
  }

  for (int i = 0; i < single_word_num_; ++i) { //init
    baseaddr_[i].hash_size = 0;
    baseaddr_[i].word_address = NULL;
  }

  word temp_word[MAX_WORDS_PER_CHARACTER];
  int word_num = 0;

  char tword1[3];
  char tword2[5];
  char tword3[7];

  word* tother3;
  word* tother2;

  unsigned int max_word_length = 0;

  for (int i = 0; i < all_words_sum_; ++i) { //deal with all words
    if (s[i].size() == 2) //single word
      continue;

    memset(tword1, 0, 3);
    memset(tword2, 0, 5);
    memset(tword3, 0, 7);

    tword1[0] = s[i][0];
    tword1[1] = s[i][1];

    tword2[0] = s[i][0];
    tword2[1] = s[i][1];

    tword3[0] = s[i][0];
    tword3[1] = s[i][1];

    word_num = 0;
    max_word_length = 0;
    int j = 0;

    for (j = i;
         j < all_words_sum_ && s[j][0] == tword1[0] && s[j][1] == tword1[1];
         ++j) { //start with the same first word
      
      if (s[j].size() >= 8) { // more than 4 word
        if (s[j][2] == tword3[2] && s[j][3] == tword3[3] && s[j][4] == tword3[4]
            && s[j][5] == tword3[5]) {
          if (s[j].size() > tother3->max_word_length)
            tother3->max_word_length = s[j].size();
          tother3->has_more = true;
          temp_word[word_num].value = s[j];
          temp_word[word_num].has_more = false;
          temp_word[word_num].is_word = true;
          temp_word[word_num].max_word_length = 0;
          ++word_num;

        } else {
          if (s[j][2] != tword2[2] || s[j][3] != tword2[3]) {
            tword2[2] = s[j][2];
            tword2[3] = s[j][3];
            tother2 = &(temp_word[word_num]);
            tother2->is_word = false;
            tother2->has_more = true; 
            tother2->max_word_length = 0;
            temp_word[word_num].value = std::string(tword2);
            ++word_num;
          } else {
            tother2->has_more = true;
          }

          tword3[2] = s[j][2];
          tword3[3] = s[j][3];
          tword3[4] = s[j][4];
          tword3[5] = s[j][5];
          tword3[6] = 0;
          tother3 = &(temp_word[word_num]);
          tother3->max_word_length = s[j].size();
          tother3->is_word = false;
          tother3->has_more = true; 

          temp_word[word_num].value = std::string(tword3);
          ++word_num;

          temp_word[word_num].value = s[j];
          ++word_num;
        }
      } else if (s[j].size() == 6) { //only 3 words
        if (s[j][2] != tword2[2] || s[j][3] != tword2[3]) {//the second word is difference
          tword2[2] = s[j][2];
          tword2[3] = s[j][3];

          tother2 = &(temp_word[word_num]);
          tother2->is_word = false; //TODO ?
          tother2->has_more = true;
          tother2->max_word_length = 0;
          temp_word[word_num].value = std::string(tword2);
          ++word_num;
        } else {
          tother2->has_more = true;
        }

        tword3[2] = s[j][2];
        tword3[3] = s[j][3];
        tword3[4] = s[j][4];
        tword3[5] = s[j][5];
        tword3[6] = '\0';

        tother3 = &(temp_word[word_num]);
        tother3->max_word_length = 6;
        tother3->is_word = true;
        tother3->has_more = false;

        temp_word[word_num].value = std::string(tword3);
        ++word_num;

      } else if (s[j].size() == 4) { 

        tword3[4] = '\0';
        tword3[5] = '\0';

        tword2[2] = s[j][2];
        tword2[3] = s[j][3];

        tother2 = &(temp_word[word_num]);
        temp_word[word_num].value = std::string(tword2);
        temp_word[word_num].is_word = true;
        temp_word[word_num].has_more = false;
        temp_word[word_num].max_word_length = 0;
        ++word_num;
      }

    } //end for

    i = j - 1;

    int char2 = (temp_word[0].value)[1];
    if (char2 < 0)
      char2 = 256 + char2;

    int char1 = (temp_word[0].value)[0];
    if (char1 < 0)
      char1 = 256 + char1;

    unsigned int index = (char1 - first_min_) * second_gap_ + char2 - second_min_;

    unsigned int temp_length = word_num * FILLFACTOR;

    baseaddr_[index].word_address = new word[temp_length];

    for (int m = 0; m < temp_length; ++m) {
      (baseaddr_[index].word_address)[m].value = "";
      (baseaddr_[index].word_address)[m].is_word = false;
      (baseaddr_[index].word_address)[m].has_more = false;
      (baseaddr_[index].word_address)[m].next = NULL;
      (baseaddr_[index].word_address)[m].max_word_length = 0;
    }

    baseaddr_[index].hash_size = temp_length;

    int sum = 0;
    word* t_word = NULL;
    for (int m = 0; m < word_num; ++m) {
      std::string temp_str = temp_word[m].value;
      sum = 0;
      for (int n = 0; n < temp_str.size(); ++n) {
        sum = (sum * 33 + temp_str[n]) % temp_length;
      }
      
      if (sum < 0)
        sum = sum + temp_length;

      if ((baseaddr_[index].word_address)[sum].value == "") {
        (baseaddr_[index].word_address)[sum].value = temp_word[m].value;

        (baseaddr_[index].word_address)[sum].has_more = temp_word[m].has_more;
        (baseaddr_[index].word_address)[sum].is_word = temp_word[m].is_word;
        (baseaddr_[index].word_address)[sum].max_word_length =
            temp_word[m].max_word_length;

        (baseaddr_[index].word_address)[sum].next = NULL;

      } else {

        word* tag_word = new word;

        tag_word->value = temp_word[m].value;
        tag_word->has_more = temp_word[m].has_more;
        tag_word->is_word = temp_word[m].is_word;
        tag_word->max_word_length = temp_word[m].max_word_length;
        tag_word->next = NULL;

        if ((baseaddr_[index].word_address)[sum].value == tag_word->value)
          continue;

        if ((baseaddr_[index].word_address)[sum].next == NULL) {
          (baseaddr_[index].word_address)[sum].next = tag_word;
        } else {
          t_word = (baseaddr_[index].word_address)[sum].next;
          while (t_word->next != NULL) {
            if (t_word->value == tag_word->value)
              break;
            t_word = t_word->next;
          }

          if (t_word->next == NULL)
            t_word->next = tag_word;

        }
      }
    } //end for int m
  } //end for int i
}

int Segment::describe(const char* tword) {

  assert(tword);
  int size = strlen(tword);
  
  assert(size >1);
  int char2 = tword[1];

  if (char2 < 0)
    char2 += 256;

  int char1 = tword[0];
  if (char1 < 0)
    char1 += 256;

  unsigned int index = (char1 - first_min_) * second_gap_ + char2 - second_min_;

  unsigned int sum = 0;
  int h_size = baseaddr_[index].hash_size;

  if (h_size == 0)
    return -1;

  for (int i = 0; i < size; ++i) {
    sum = ((sum << 5) + sum + tword[i]) % h_size; //times 33
  }
  if (sum < 0)
    sum = sum + h_size;

  word* target = &((baseaddr_[index].word_address)[sum]);
  if (size == 4) {
    if (target->value == "") {
      return -1;
    }
    while (target != NULL) {
      if (strcmp(target->value.c_str(), tword) == 0)
        break;
      target = target->next;
    }

    if (target == NULL)
      return -1;
    if (target->is_word && target->has_more)
      return 1;
    if (target->is_word && !target->has_more)
      return 0;
    if (!target->is_word && target->has_more)
      return 2;

  }
  if (size == 6) {
    if (target->value == "")
      return -1;
    while (target != NULL) {
      if (strcmp(target->value.c_str(), tword) == 0)
        break;
      target = target->next;
    }
    if (target == NULL)
      return -1;
    if (target->is_word && target->has_more)
      return target->max_word_length;
    if (target->is_word && !target->has_more)
      return 0;
    if (!target->is_word && target->has_more)
      return 0 - target->max_word_length;
  }

  if (target->value == "")
    return -1;

  while (target != NULL) {
    if (strcmp(target->value.c_str(), tword) == 0)
      break;
    target = target->next;
  }
  if (target == NULL)
    return -1;
  else
    return 0;
  return -1;
}

int Segment::segment(const char* ttext, std::vector<SegRes>& result) {

  strcpy(text_, ttext);
  int index = 0;
  //int result_num = 0;

  map_res_.clear();

  bool isSeperator = true;
  int i = 0;

  convert_sentence(text_);

  while (text_[i] != '\0') {

    isSeperator = true;

    index = 0;
    while (1) {
      if (text_[i] < 0) {
        buffer_[index++] = text_[i++];
        if (text_[i] == '\0') {
          std::cerr << "encode error!" << std::endl;
          return 0;
        }
        buffer_[index++] = text_[i++];
      } else
        break;
    }

    if (index > 1) {
      buffer_[index] = '\0';
      isSeperator = false;
      if (index % 2 != 0) {
        std::cerr << "encode error!" << std::endl;
        return 0;
      } else {
        int from = 0;
        while (from < index) { 

          if (index - from == 2) {
            do_segment(buffer_ + from);
            break;
          }

          for (int k = 0; k < index + 1; ++k)
            temp_ch_[k] = '\0'; 

          for (int k = 0; k < 4; ++k)
            temp_ch_[k] = buffer_[from + k];

          int r2 = describe(temp_ch_);

          if (r2 == -1) { 
            temp_ch_[2] = '\0';
            do_segment(temp_ch_);
            from = from + 2;
            continue;
          } else if (r2 == 0) {
            do_segment(temp_ch_, 'C');
            from = from + 4;
            continue;
          } else if (r2 == 1) {
            if ((index - from) == 4) {
              do_segment(temp_ch_, 'C');
              break;
            }

            temp_ch_[4] = buffer_[from + 4];
            temp_ch_[5] = buffer_[from + 5];

            int r3 = describe(temp_ch_); //increase to 3 words

            if (r3 == -1) { 
              temp_ch_[4] = '\0';
              do_segment(temp_ch_, 'C');
              from = from + 4;
              continue;
            }
            if (r3 == 0) { 
              do_segment(temp_ch_, 'C');
              from = from + 6;
              continue;
            }
            if (r3 > 0) { 
              if (index - from == 6) {
                do_segment(temp_ch_, 'C');
                break;
              }
              int t_mlength = index - from;
              if (t_mlength > r3)
                t_mlength = r3;
              for (int n = 6; n < t_mlength; n++) 
                temp_ch_[n] = buffer_[from + n];

              int location = t_mlength - 1;
              int r4 = -1;
              while (location > 6) { //do FFM
                r4 = describe(temp_ch_);
                if (r4 == 0) {
                  do_segment(temp_ch_, 'C');
                  from = from + location + 1;
                  break;
                }
                temp_ch_[location] = '\0';
                temp_ch_[location - 1] = '\0';
                location -= 2;
              }
              if (r4 == 0)
                continue;
              temp_ch_[6] = '\0';
              do_segment(temp_ch_, 'C');
              from = from + 6;
              continue;
            }
            if (r3 < 0) {
              if (index - from == 6) {
                temp_ch_[4] = '\0';
                do_segment(temp_ch_, 'C');
                do_segment(buffer_ + index - 2);
                break;
              }

              int t_mlength = index - from;
              if (t_mlength + r3 > 0)
                t_mlength = 0 - r3;

              for (int n = 6; n < t_mlength; n++)
                temp_ch_[n] = buffer_[from + n];
              temp_ch_[t_mlength] = 0;

              int location = t_mlength - 1;
              int r4 = -1;
              while (location > 6) {
                r4 = describe(temp_ch_);
                if (r4 == 0) {
                  do_segment(temp_ch_, 'C');
                  from = from + location + 1;
                  break;
                }
                temp_ch_[location] = 0;
                temp_ch_[location - 1] = 0;
                location -= 2;
              }
              if (r4 == 0)
                continue;

              temp_ch_[4] = '\0';
              do_segment(temp_ch_, 'C');
              from = from + 4;
              continue;
            }
          } else if (r2 == 2) { //not word but prefix
            char t_ch[3];
            t_ch[0] = temp_ch_[0];
            t_ch[1] = temp_ch_[1];
            t_ch[2] = '\0';
            std::string temp_str(t_ch);
            if ((index - from) == 4) {
              char temp_ch_[2] = { '\0' };
              do_segment(temp_ch_);
              do_segment(buffer_ + index - 2);
              break;
            }

            temp_ch_[4] = buffer_[from + 4];
            temp_ch_[5] = buffer_[from + 5];

            int r3 = describe(temp_ch_);

            if (r3 == -1) {
              do_segment(t_ch);
              from = from + 2;
              continue;
            }
            if (r3 == 0) {
              do_segment(temp_ch_, 'C');
              from = from + 6;
              continue;
            }
            if (r3 > 0) {
              if (index - from == 6) {
                do_segment(temp_ch_, 'C');
                break;
              }
              int t_mlength = index - from;
              if (t_mlength > r3)
                t_mlength = r3;
              for (int n = 6; n < t_mlength; n++)
                temp_ch_[n] = buffer_[from + n];
              int location = t_mlength - 1;
              int r4 = -1;
              while (location > 6) {
                r4 = describe(temp_ch_);
                if (r4 == 0) {
                  do_segment(temp_ch_, 'C');
                  from = from + location + 1;
                  break;
                }
                temp_ch_[location] = '\0';
                temp_ch_[location - 1] = '\0';
                location -= 2;
              }
              if (r4 == 0)
                continue;
              temp_ch_[6] = '\0';
              do_segment(temp_ch_, 'C');
              from = from + 6;
              continue;
            }
            if (r3 < 0) {
              if (index - from == 6) {
                temp_ch_[2] = '\0';
                do_segment(temp_ch_);
                from = from + 2;
                continue;
              }
              int t_mlength = index - from;
              if (t_mlength + r3 > 0)
                t_mlength = 0 - r3;

              for (int n = 6; n < t_mlength; n++)
                temp_ch_[n] = buffer_[from + n];

              int location = t_mlength - 1;
              int r4 = -1;
              while (location > 6) {
                r4 = describe(temp_ch_);
                if (r4 == 0) {
                  do_segment(temp_ch_, 'C');
                  from = from + location + 1;
                  break;
                }
                temp_ch_[location] = 0;
                temp_ch_[location - 1] = 0;
                location -= 2;
              }
              if (r4 == 0)
                continue;

              temp_ch_[2] = '\0';
              do_segment(temp_ch_);
              from = from + 2;
              continue;
            }
          }
        }
      }
    } //end if (index > 1)

    //now, handle english & num
    index = 0;
    bool all_num = true;
    bool all_char = true;

    while (text_[i] != '\0'
        && ((text_[i] >= 'a' && text_[i] <= 'z')
            || (text_[i] >= 'A' && text_[i] <= 'Z')
            || (text_[i] >= '0' && text_[i] <= '9'))) {
      if ((text_[i] >= 'a' && text_[i] <= 'z')
          || (text_[i] >= 'A' && text_[i] <= 'Z')) {
        if (text_[i] < 'a')
          buffer_[index++] = text_[i++] + 32; //tolower
        else
          buffer_[index++] = text_[i++];
        all_num = false;
      } else {
        buffer_[index++] = text_[i++];
        all_char = false;
      }
    }

    if (index > 0) { //at least 1 char or 1 num
      char type = ' ';
      buffer_[index] = '\0';
      isSeperator = false;

      if (all_num) {
        if (index == 1)
          type = 'n';
        else
          type = 'N';
      } else if (all_char) {
        if (index == 1)
          type = 's';
        else
          type = 'S';
      } else
        type = 'x';
      do_segment(buffer_, type);
    }

    if (isSeperator) { 
      char tmp[2] = { '\0' };
      tmp[0] = *(text_ + i);

      do_segment(tmp, 'e');
      ++i;
    }

  } //end while (text_[i] != 0)  

  result.clear();
  std::map<std::string, std::pair<char, int> >::iterator it = map_res_.begin();
  for (; it != map_res_.end(); ++it) {
    if (it->second.first == 'c' || it->second.first == 'C'
        || it->second.first == 's' || it->second.first == 'S') {
      if (is_stop_word(it->first))
        it->second.first = 't';
    }
    SegRes sr = { it->first, it->second.first, it->second.second };
    result.push_back(sr);
  }

  return result.size();
}

void Segment::do_segment(const char* word, char type) {
  if (word == NULL)
    return;
  std::string key = word;
  std::map<std::string, std::pair<char, int> >::iterator it;
  it = map_res_.find(key);
  if (it == map_res_.end()) {
    if (type == ' ') {
      if (word[0] > -96 && word[0] < -86)
        type = 'm';
      else
        type = 'c';
    }
    std::pair<char, int> p(type, 1);
    map_res_.insert(make_pair(key, p));
  } else {
    ++it->second.second;
  }
}

